﻿using Consul;
using System.Net;
using System.Text.Json;

namespace XxlJobDotNetCore.Extension
{
    public static class ConfigurationBuilderExtension
    {
        public static IConfigurationBuilder AddConsulConfiguration(this IConfigurationBuilder configurationBuilder, ConsulConfig config, bool reloadOnChanges = false)
        {
            ConsulConfig config2 = config;
            ConsulClient configClient = new ConsulClient(delegate (ConsulClientConfiguration client)
            {
                client.Address = new Uri(config2.ConsulUrl);
            });
            string[] array = config2.ConsulKeyPath.Split(",", StringSplitOptions.RemoveEmptyEntries);
            foreach (string consulKeyPath in array)
            {
                configurationBuilder.Add(new DefaultConsulConfigurationSource(configClient, consulKeyPath, reloadOnChanges));
            }
            return configurationBuilder;
        }
    }
    public class DefaultConsulConfigurationSource : IConfigurationSource
    {
        private readonly ConsulClient _configClient;

        private readonly string _consulKeyPath;

        private readonly bool _reloadOnChanges;

        public DefaultConsulConfigurationSource(ConsulClient configClient, string consulKeyPath, bool reloadOnChanges)
        {
            _configClient = configClient;
            _consulKeyPath = consulKeyPath;
            _reloadOnChanges = reloadOnChanges;
        }

        public IConfigurationProvider Build(IConfigurationBuilder builder)
        {
            return new DefaultConsulConfigurationProvider(_configClient, _consulKeyPath, _reloadOnChanges);
        }
    }
    public sealed class DefaultConsulConfigurationProvider : ConfigurationProvider
    {
        private readonly ConsulClient _consulClient;

        private readonly string _path;

        private readonly int _waitMillisecond;

        private readonly bool _reloadOnChange;

        private ulong _currentIndex;

        private Task? _pollTask;

        public DefaultConsulConfigurationProvider(ConsulClient consulClient, string consulKeyPath, bool reloadOnChanges)
        {
            _consulClient = consulClient;
            _path = consulKeyPath;
            _waitMillisecond = 1000;
            _reloadOnChange = reloadOnChanges;
        }

        public override void Load()
        {
            if (_pollTask == null)
            {
                LoadData(GetData().GetAwaiter().GetResult());
                PollReaload();
            }
        }

        private void LoadData(QueryResult<KVPair> queryResult)
        {
            _currentIndex = queryResult.LastIndex;
            if (queryResult.Response != null && queryResult.Response.Value != null && queryResult.Response.Value.Any())
            {
                Stream input = new MemoryStream(queryResult.Response.Value);
                base.Data = JsonConfigurationFileParser.Parse(input);
            }
        }

        private async Task<QueryResult<KVPair>> GetData()
        {
            QueryResult<KVPair> queryResult = await _consulClient.KV.Get(_path);
            if (queryResult.StatusCode == HttpStatusCode.OK || queryResult.StatusCode == HttpStatusCode.NotFound)
                return queryResult;
            throw new Exception($"Error loading configuration from consul. Status code: {queryResult.StatusCode}.");
        }

        private void PollReaload()
        {
            if (!_reloadOnChange)
            {
                return;
            }

            _pollTask = Task.Run(async delegate
            {
                while (true)
                {
                    QueryResult<KVPair> queryResult = await GetData();
                    if (queryResult.LastIndex != _currentIndex)
                    {
                        LoadData(queryResult);
                        OnReload();
                    }

                    await Task.Delay(_waitMillisecond);
                }
            });
        }
    }
    internal class JsonConfigurationFileParser
    {
        private readonly IDictionary<string, string> _data = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);

        private readonly Stack<string> _context = new Stack<string>();

        private string _currentPath = string.Empty;

        private JsonConfigurationFileParser()
        {
        }

        public static IDictionary<string, string> Parse(Stream input)
        {
            return new JsonConfigurationFileParser().ParseStream(input);
        }

        private IDictionary<string, string> ParseStream(Stream input)
        {
            _data.Clear();
            JsonDocumentOptions jsonDocumentOptions = default(JsonDocumentOptions);
            jsonDocumentOptions.CommentHandling = JsonCommentHandling.Skip;
            jsonDocumentOptions.AllowTrailingCommas = true;
            JsonDocumentOptions options = jsonDocumentOptions;
            using (StreamReader streamReader = new StreamReader(input))
            {
                using JsonDocument jsonDocument = JsonDocument.Parse(streamReader.ReadToEnd(), options);
                if (jsonDocument.RootElement.ValueKind != JsonValueKind.Object)
                {
                    throw new FormatException("data is invalid");
                }

                VisitElement(jsonDocument.RootElement);
            }

            return _data;
        }

        private void VisitElement(JsonElement element)
        {
            foreach (JsonProperty item in element.EnumerateObject())
            {
                EnterContext(item.Name);
                VisitValue(item.Value);
                ExitContext();
            }
        }

        private void VisitValue(JsonElement value)
        {
            switch (value.ValueKind)
            {
                case JsonValueKind.Object:
                    VisitElement(value);
                    break;
                case JsonValueKind.Array:
                    {
                        int num = 0;
                        foreach (JsonElement item in value.EnumerateArray())
                        {
                            EnterContext(num.ToString());
                            VisitValue(item);
                            ExitContext();
                            num++;
                        }

                        break;
                    }
                case JsonValueKind.String:
                case JsonValueKind.Number:
                case JsonValueKind.True:
                case JsonValueKind.False:
                case JsonValueKind.Null:
                    {
                        string currentPath = _currentPath;
                        if (_data.ContainsKey(currentPath))
                        {
                            throw new FormatException("duplicate key[" + currentPath + "]");
                        }

                        _data[currentPath] = value.ToString();
                        break;
                    }
                default:
                    throw new FormatException("Formater is invalid");
            }
        }

        private void EnterContext(string context)
        {
            _context.Push(context);
            _currentPath = ConfigurationPath.Combine(_context.Reverse());
        }

        private void ExitContext()
        {
            _context.Pop();
            _currentPath = ConfigurationPath.Combine(_context.Reverse());
        }
    }
}
